#include <boost/regex.hpp>
#include <boost/filesystem.hpp>
#include <boost/date_time/gregorian/gregorian.hpp>
#include <list>
#include <string>
#include <cstdint>
#include <exception>
#include <istream>
#include <fstream>
#include <sstream>

using namespace std;
using namespace boost;
using namespace filesystem;

/*! To catch any issue */
class Exception : public std::exception
{
public:
  Exception(const string &msg) throw() : msg(msg) {}
  Exception(const Exception &other) throw() : msg(other.msg) {}
  ~Exception(void) throw() {}
  Exception &operator= (const Exception &other) throw() {
    this->msg = other.msg;
    return *this;
  }
  const char *what(void) const throw() { return msg.c_str(); }
private:
  string msg; //!< String message
};

/*! Helper string */
static const char usage[] = {
  "Usage: cb -m MATCH [OPTIONS]\n"
  "Find regular expression in the code and possibly substitute\n"
  "found occurences by given format string.\n\n"
  "Arguments:\n"
  "  -m, --match REGEX      provide REGEX to search in files\n"
  "  -f, --file REGEX       provide (optional) REGEX to match for the\n"
  "                         file name\n"
  "  -r, --replace STRING   provide (optional) format string STRING\n"
  "                         for the replacement\n"
  "  -h                     activate highlighting while searching\n"
  "  -R                     recurse directories\n"
};

enum conAttribute : int {
  RESET = 0,
  BRIGHT = 1,
  DIM = 2,
  UNDERLINE = 3,
  BLINK = 4,
  REVERSE = 7,
  HIDDEN = 8
};

enum conColor : int {
  BLACK = 0,
  RED = 1,
  GREEN = 2,
  YELLOW = 3,
  BLUE = 4,
  MAGENTA = 5,
  CYAN = 6,
  WHITE = 7
};

struct TextColor {
  TextColor(conAttribute attr, conColor fg, conColor bg) :
    attr(attr), fg(fg), bg(bg) {}
  operator string(void) const {
    char command[13];
    sprintf(command, "%c[%d;%d;%dm", 0x1B, attr, fg + 30, bg + 40);
    return command;
  }
  conAttribute attr;
  conColor fg;
  conColor bg;
};

ostream &operator<< (ostream &out, const TextColor &tc) {
  return out << string(tc);
}

/*! To browse the files */
struct Browser
{
  /*! Initialize it */
  Browser(int argc, char *argv[]);
  /*! Process the given path (can be anything) */
  void process(const path &p, uint32_t depth);
  /*! Process the file */
  void processFile(const path &p);
  regex matchRE;      //!< String to match in the file (mandatory)
  regex fileRE;       //!< Regex to filter files
  string replacement; //!< Replacemement strings
  string hightlight;  //!< Hightlighting format string
  uint32_t hasFile:1; //!< If 0, all of them
  uint32_t hasReplace:1;   //!< We ask a replacement in the files
  uint32_t hasHightlight:1;//!< Display the matched string in red
  uint32_t maxDepth;       //!< Recursion depth (default 1 == no recursion)
};

Browser::Browser(int argc, char *argv[])
{
  uint32_t hasMatch = 0;
  hasHightlight = hasFile = hasReplace = 0;
  maxDepth = 1;

  // Set up all the options
  if (argc < 2) throw Exception(string(usage));
  for (int i = 1; i < argc; ) {
#define DECL_OPTION(NAME, OPT, OPTION, OPTION_COND)\
    if (string(argv[i]) == "--"#NAME || string(argv[i]) == "-"#OPT) { \
      if (i+1 == argc) throw Exception(usage);\
      OPTION = argv[i+1];                     \
      OPTION_COND = 1;                        \
      ++i;                                    \
    }
    DECL_OPTION(match, m, matchRE, hasMatch)
    else DECL_OPTION(replace, r, replacement, hasReplace)
    else DECL_OPTION(file, f, fileRE, hasFile)
    else if (argv[i][0] == '-') {
      const size_t len = strlen(argv[i]);
      if (len == 0) throw Exception(usage);
      for (uint32_t opt = 1; opt < len; ++opt) {
        if (argv[i][opt] == 'R') maxDepth = 0xffffffff;
        else if (argv[i][opt] == 'h') hasHightlight = 1;
        else throw Exception(usage);
      }
    }
    else throw Exception(usage);
    ++i;
#undef DECL_OPTION
  }
  if (!hasMatch)
    throw Exception(string("Matching regex is expected.\n") + usage);

  // Highlighting is used when looking for a text
  stringstream ss;
  ss << "(" << TextColor(BRIGHT, RED, BLACK)
     << "$&" << TextColor(RESET, WHITE, BLACK)
     << ")";
  hightlight = ss.str();
}

void Browser::processFile(const path &p)
{
  ifstream src;
  ofstream dst;

  // Save the file if replacement is required
  if (hasReplace) {
    gregorian::date d = gregorian::day_clock::local_day();
    const path copyPath = p.native() + "." + gregorian::to_iso_string(d) + ".bak";
    if (exists(copyPath)) remove(copyPath); // remove a previous copy
    copy(p, copyPath); // make the copy
    remove(p); // remove the source file also
    src.open(copyPath.native());
    dst.open(p.native());
  } else
    src.open(p.native());

  if (src.is_open()) {
    int lineno = 1;
    bool alreadyFoundSomething = false;
    while (src.good()) {
      string line;
      getline (src,line);
      // No replacement -> we only print what we found
      if (!hasReplace) {
        if (regex_search(line, matchRE)) {
          if (!alreadyFoundSomething) {
            cout << p.native() << endl;
            alreadyFoundSomething = true;
          }
          if (hasHightlight)
            line = regex_replace(line, matchRE, hightlight, format_all);
          cout << "  line " << lineno << ", " << line << endl;
        }
      }
      // Replacement required -> we output everything in the new file
      else {
        line = regex_replace(line, matchRE, replacement, format_all);
        dst << line << std::endl;
      }
      lineno++;
    }
    if (alreadyFoundSomething) cout << endl;
    src.close();
    if (dst.is_open()) dst.close();
  }
  else throw Exception("Unable to open file: \"" + p.native() + "\"");
}

void Browser::process(const path &p, uint32_t depth)
{
  try {
    if (exists(p)) {
      if (is_regular_file(p)) {
        if (!hasFile || regex_match(p.filename().native(), fileRE))
          processFile(p);
      } else if (is_directory(p) && depth < maxDepth) {
        for (auto it = directory_iterator(p); it != directory_iterator(); ++it)
          process(*it, depth + 1);
      }
    }
  }
  catch (const filesystem_error& ex) {
    cout << ex.what() << '\n';
  }
}

int main(int argc, char *argv[])
{
  try {
    Browser browser(argc, argv);
    browser.process("./", 0);
  }
  // Something bad happened
  catch (Exception e) {
    cout << argv[0] << ": " << e.what() << endl;
    return -1;
  }
  return 0;
}

